library(readr)
library(readxl)
library(tidyverse)
library(ggplot2)
library(hrbrthemes)
library(dplyr)
library(tidyr)
library(viridis)
library(ggpubr)
setwd("~/PostDoc/human-metnet/results/results_analysis_in_R_rmd")
dflist_names = c('fold_change_aritmetic', 'fold_change_geometric', 'fold_change_quadratic', 'fold_change_harmonic',
'delta_aritmetic', 'delta_geometric', 'delta_quadratic', 'delta_harmonic')
read_sheets <- function(a_sheet){df <- read_excel('delta_fc_centralidades.xlsx', sheet = a_sheet) %>% column_to_rownames("...1")
return(df)}
map(dflist_names, read_sheets) %>% purrr::set_names(dflist_names) -> list_of_cents
names(list_of_cents)
list_of_cents$fold_change_aritmetic %>% names
Density plot of centralities
plot_density <- function(df, titulo ){cut = 0.01
df %>% gather() -> df_gathered
df_gathered['value'] %>% filter(abs(value) > cut) %>%
ggplot( aes(x=value)) +
geom_density(fill="deepskyblue4", color="azure4", alpha=.8)+
theme(legend.position="top") +
ylab("Density") + ggtitle(titulo) +
xlab("Centrality") + geom_vline(xintercept=0, linetype="dashed", color = "red", size=.4) -> Density_plot_of_centralities
return(Density_plot_of_centralities)}
map2(list_of_cents, names(list_of_cents), plot_density)
$fold_change_aritmetic
$fold_change_geometric
$fold_change_quadratic
$fold_change_harmonic
$delta_aritmetic
$delta_geometric
$delta_quadratic
$delta_harmonic








#calcular std, kurtonis y skewness.
#centralities <- Results[,!is.na(as.numeric(colnames(Results)))] %>% gather()
#Density_plot_of_centralities.panel <- ggarrange( NULL,Density_plot_of_centralities, nrow = 2, heights= c(1, .4), labels = c('a','b'),
# hjust = -0.1, vjust = .8)
#Density_plot_of_centralities.panel
fba <- read.csv('/home/alejandro/NAMU_in_progress/Figures_creation/Code/Results_Acevedo_et_al_2021/01_phpps_bipartite_fluxes_sensis_optimality/FBA_results.csv') %>%
select(c('ID','Name', 'Reaction', 'Flux', 'Sensitivity')) %>% column_to_rownames('ID')
append_fba <- function(cent){
hola <- rownames_to_column(fba) %>% inner_join(rownames_to_column(cent)) %>% column_to_rownames('rowname')
return(hola)
}
map(list_of_cents, append_fba) -> fba_list_of_cents
Joining, by = "rowname"
Joining, by = "rowname"
Joining, by = "rowname"
Joining, by = "rowname"
Joining, by = "rowname"
Joining, by = "rowname"
Joining, by = "rowname"
Joining, by = "rowname"
Hierarchical clustering
library(tidyverse)
library(scales)
assign_node_type <- function(df){
data <- df %>% rownames_to_column('ID') %>% filter(!str_detect(ID, regex('AA_|AA2', ignore_case = F))) %>%
mutate(`Node type` = ifelse(str_detect(Reaction, regex('\\[[a-z]A\\]', ignore_case = T)), 'Astrocyte', NA)) %>%
mutate(`Node type` = ifelse(str_detect(Reaction, regex('\\[[a-z]N\\]', ignore_case = T)), 'Neuron', `Node type`)) %>%
mutate(`Node type` = ifelse( str_detect(Reaction, regex('\\[[a-z]A\\]|\\[[a-z]N\\]',ignore_case = T), negate = T), 'Exchange', `Node type`)) %>%
mutate(`Node type` = ifelse(str_detect(Reaction, regex('\\[[a-z]A\\]',ignore_case = T)) &
str_detect(Name, regex('DM_|Demand|sink',ignore_case = T)), 'Sink/Demand Astro', `Node type`))%>%
mutate(`Node type` = ifelse(str_detect(Reaction, regex('\\[[a-z]N\\]',ignore_case = T)) &
str_detect(Name, regex('DM_|Demand|sink',ignore_case = T)), 'Sink/Demand Neuron', `Node type`))
return(data)}
map(fba_list_of_cents, assign_node_type) -> data
clean_centralities <- function(df){ df %>% select(-c ("ID", "Name", "Reaction", "Flux", "Sensitivity","Node type") )}
map(data, clean_centralities) -> only_centralities
glimpse(data)
data
$fold_change_aritmetic
$fold_change_geometric
$fold_change_quadratic
$fold_change_harmonic
$delta_aritmetic
$delta_geometric
$delta_quadratic
$delta_harmonic
NA
get_total_centralities <- function(df){df %>% abs %>% rowSums }
map(only_centralities, get_total_centralities) -> total.abs.centrality
get_summaryzed.optimality <- function(df){ pseudoLog10 <- function(x) { asinh(x/2)/log(10) }
df$Flux %>% abs %>% as.matrix() %>% pseudoLog10 %>% rescale(c(0,1)) -> fluxes
df$Sensitivity %>% abs %>% as.matrix() %>% pseudoLog10 %>% rescale(c(0,1))-> sensitivities
fluxes + sensitivities -> summaryzed.optimality
return(summaryzed.optimality)
}
map( data,get_summaryzed.optimality) -> summaryzed.optimality
heatmap
library(ComplexHeatmap)
library(WGCNA)
get_correlation_matrices <-function(df){
#scale_rows <- function(x){t(scale(t(x)))}
df %>% transposeBigData %>% cor(method = "kendall") -> hola
return(hola)}
map(only_centralities, get_correlation_matrices) -> my.corr.matrices
#all.equal(data$fold_change_aritmetic$`Node type` , data$delta_quadratic$`Node type`)
#all.equal(data$fold_change_harmonic$`Node type` , data$delta_geometric$`Node type`)
node_types <- data$fold_change_aritmetic$`Node type`
set.seed(1)
my.corr.matrices$fold_change_geometric -> my.corr.mat
left_annotation <- rowAnnotation(`Nodes` = node_types, col = list( `Nodes` = c(
"Astrocyte" = "deepskyblue3",
'Exchange' = 'darkgreen',
'Sink/Demand Astro'='darkgoldenrod',
"Neuron" = "brown2",
'Sink/Demand Neuron'= 'blueviolet' )))
total.abs.centrality$fold_change_aritmetic -> a_total.abs.cent
summaryzed.optimality$fold_change_aritmetic -> a_summaryzed.opti
right_annotation <-
rowAnnotation(gap = unit(12, "points"),Ce = anno_barplot(bar_width = 0.01,width = unit(1.5, "cm"), border = T,a_total.abs.cent, gp = gpar(col = 'azure4')),
Op = anno_barplot(bar_width = 0.01,width = unit(1.5, "cm") ,border = T, a_summaryzed.opti, gp = gpar(col = 'azure4')))
ht <- Heatmap(my.corr.mat, name = "Correlation", left_annotation =left_annotation,
right_annotation=right_annotation,
clustering_distance_columns = function(m) dist(m, method = 'euclidean'),
cluster_columns = function(x) fastcluster::hclust(dist(x), "median"),
clustering_distance_rows = function(m) dist(m, method = 'euclidean'),
cluster_rows = function(x) fastcluster::hclust(dist(x), "median"),
row_km = 2,
column_km = 2,
# row_split = paste0("cluster ", pa$clustering),
# column_split = paste0("cluster ", pa$clustering),
border = TRUE,
row_dend_width = unit(3, "cm"),
row_gap = unit(2, "mm"),
column_gap = unit(2, "mm"),
width = unit(10, "cm"),
height = unit(10, "cm"),
column_title = c("Astrocytic cluster", "Neuronal cluster"),
column_title_gp = gpar(fontsize = 10),
row_title_rot = 0,
show_column_names = F,
show_row_names = F,
row_names_gp = gpar(fontsize = 8),
row_title = c("Astrocytic\n cluster", "Neuronal\n cluster"),
row_title_gp = gpar(fontsize = 10))
my_heatmap = grid.grabExpr(draw(ht))
ht

upper_panel <- ggarrange( Density_plot_of_centralities.panel, my_heatmap, ncol = 2, widths = c(.4, 1), heights = c(1,.3), labels = c('','c'),
hjust = -4, vjust = .8)
upper_panel

Distribution of pairwise correlations
ggplot(quads, aes(x=Comparison, y=`Node correlation`, fill=Comparison)) +
geom_violin(trim=F, colour = "azure4")+
stat_compare_means( vjust= -1., hjust= 0, comparisons = my_comparisons, method = "wilcox.test", p.adjust.method = "bonferroni", label = "p.signif")+
scale_fill_manual(values = alpha(c("deepskyblue3", "blueviolet", "brown2"), .8)) +
theme(plot.title = element_text(hjust = 0.5),legend.position="none", axis.title.x=element_blank(),
axis.text.y = element_text(angle = 45, hjust = 1), axis.text.x = element_text(angle = 50, hjust = 1)) +
#ggtitle("Neuronal self-correlations") +
scale_y_continuous(limits=c(-0.7, 2)) + geom_hline(yintercept=0, linetype="dashed", color = "darkred", size=1)-> corr_comparisons
corr_comparisons

library(ProjectionBasedClustering)
Registered S3 method overwritten by 'htmlwidgets':
method from
print.htmlwidget tools:rstudio
library(PCAtools)
Loading required package: ggrepel
Attaching package: ‘PCAtools’
The following objects are masked from ‘package:stats’:
biplot, screeplot
library(magrittr)
Attaching package: ‘magrittr’
The following object is masked from ‘package:purrr’:
set_names
The following object is masked from ‘package:tidyr’:
extract
p <- pca(my.corr.mat)
cbind(p$rotated$PC1, p$rotated$PC2) %>% as.matrix %>% as.data.frame-> my_pca
my_pca %>% cbind( data$`Node type`) %>% set_colnames(c('PC1','PC2','node')) -> to.pca.scatter
summaryzed.optimality -> Op
total.abs.centrality -> Centrality
Centrality -> Ce
b <- ggplot(to.pca.scatter, aes(x = PC1, y = PC2))
b + scale_color_manual(labels = c("Astro", "Exch",'Neu','Si/De Ast', 'Si/De Neu'), values = c("deepskyblue3",'darkgreen','brown2','darkgoldenrod','blueviolet')) +
theme(legend.position="right", legend.box = "vertical")+ geom_point(aes(size = Op, color = node))-> pca_node_RedCosts
b <- ggplot(to.pca.scatter, aes(x = PC1, y = PC2))
b + scale_color_manual(labels = c("Astro", "Exch",'Neu','Si/De Ast', 'Si/De Neu'), values = c("deepskyblue3",'darkgreen','brown2','darkgoldenrod','blueviolet')) +
theme(legend.position="right", legend.box = "vertical")+ geom_point(aes( size = Ce,color = node))-> pca_node_centrality
bottom_panel <- ggarrange( corr_comparisons,pca_node_centrality, pca_node_RedCosts , ncol = 3, widths = c(.4,1,1) , labels = c('d','e','f'),
hjust = 0, vjust = 1)
Removed 3 rows containing non-finite values (stat_ydensity).Removed 3 rows containing non-finite values (stat_signif).Removed 26 rows containing missing values (geom_violin).

ggsave(file="panel_without_graph.png", plot=panel_without_graph, width=13, height=9, dpi = 320)
Node types for Networkx (python) attributes
library(tidyverse)
library(magrittr)
Results <- read_csv("Results.csv")
data_Networkx <- Results%>%
mutate(`Node_type` = ifelse(str_detect(Formula, regex('\\[[a-z]A\\]', ignore_case = T)), 'Astrocyte', NA)) %>%
mutate(`Node_type` = ifelse(str_detect(Formula, regex('\\[[a-z]N\\]', ignore_case = T)), 'Neuron', `Node_type`)) %>%
mutate(`Node_type` = ifelse( str_detect(Formula, regex('\\[[a-z]A\\]|\\[[a-z]N\\]',ignore_case = T), negate = T), 'Exchange', `Node_type`)) %>%
mutate(`Node_type` = ifelse(str_detect(Formula, regex('\\[[a-z]A\\]',ignore_case = T)) &
str_detect(Name, regex('DM_|Demand|sink',ignore_case = T)), 'Sink/Demand Astro', `Node_type`))%>%
mutate(`Node_type` = ifelse(str_detect(Formula, regex('\\[[a-z]N\\]',ignore_case = T)) &
str_detect(Name, regex('DM_|Demand|sink',ignore_case = T)), 'Sink/Demand Neuron', `Node_type`))
data_Networkx %<>% select(c(ID, Node_type))
write_csv(data_Networkx, 'data_Networkx.csv')
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShocmJydGhlbWVzKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHZpcmlkaXMpCmxpYnJhcnkoZ2dwdWJyKQpzZXR3ZCgifi9Qb3N0RG9jL2h1bWFuLW1ldG5ldC9yZXN1bHRzL3Jlc3VsdHNfYW5hbHlzaXNfaW5fUl9ybWQiKQpkZmxpc3RfbmFtZXMgPSBjKCdmb2xkX2NoYW5nZV9hcml0bWV0aWMnLCAnZm9sZF9jaGFuZ2VfZ2VvbWV0cmljJywgJ2ZvbGRfY2hhbmdlX3F1YWRyYXRpYycsICdmb2xkX2NoYW5nZV9oYXJtb25pYycsCiAgICAnZGVsdGFfYXJpdG1ldGljJywgJ2RlbHRhX2dlb21ldHJpYycsICdkZWx0YV9xdWFkcmF0aWMnLCAnZGVsdGFfaGFybW9uaWMnKQoKcmVhZF9zaGVldHMgPC0gZnVuY3Rpb24oYV9zaGVldCl7ZGYgPC0gcmVhZF9leGNlbCgnZGVsdGFfZmNfY2VudHJhbGlkYWRlcy54bHN4Jywgc2hlZXQgPSBhX3NoZWV0KSAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCIuLi4xIikKICAgICAgICAgICAgICAgICAgcmV0dXJuKGRmKX0KCm1hcChkZmxpc3RfbmFtZXMsIHJlYWRfc2hlZXRzKSAlPiUgcHVycnI6OnNldF9uYW1lcyhkZmxpc3RfbmFtZXMpIC0+IGxpc3Rfb2ZfY2VudHMKbmFtZXMobGlzdF9vZl9jZW50cykKCmxpc3Rfb2ZfY2VudHMkZm9sZF9jaGFuZ2VfYXJpdG1ldGljICU+JSBuYW1lcwpgYGAKCkRlbnNpdHkgcGxvdCBvZiBjZW50cmFsaXRpZXMKYGBge3IgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCgpwbG90X2RlbnNpdHkgPC0gZnVuY3Rpb24oZGYsIHRpdHVsbyApe2N1dCA9IDAuMDEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZiAlPiUgZ2F0aGVyKCkgLT4gZGZfZ2F0aGVyZWQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGZfZ2F0aGVyZWRbJ3ZhbHVlJ10gJT4lIGZpbHRlcihhYnModmFsdWUpID4gY3V0KSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2dwbG90KCBhZXMoeD12YWx1ZSkpICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VvbV9kZW5zaXR5KGZpbGw9ImRlZXBza3libHVlNCIsIGNvbG9yPSJhenVyZTQiLCBhbHBoYT0uOCkrCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIikgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5bGFiKCJEZW5zaXR5IikgKyBnZ3RpdGxlKHRpdHVsbykgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4bGFiKCJDZW50cmFsaXR5IikgKyBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MCwgbGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yID0gInJlZCIsICBzaXplPS40KSAtPiBEZW5zaXR5X3Bsb3Rfb2ZfY2VudHJhbGl0aWVzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybihEZW5zaXR5X3Bsb3Rfb2ZfY2VudHJhbGl0aWVzKX0KCm1hcDIobGlzdF9vZl9jZW50cywgbmFtZXMobGlzdF9vZl9jZW50cyksIHBsb3RfZGVuc2l0eSkKYGBgCgoKYGBge3IgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KI2NlbnRyYWxpdGllcyA8LSAgUmVzdWx0c1ssIWlzLm5hKGFzLm51bWVyaWMoY29sbmFtZXMoUmVzdWx0cykpKV0gJT4lIGdhdGhlcigpCgojRGVuc2l0eV9wbG90X29mX2NlbnRyYWxpdGllcy5wYW5lbCA8LSBnZ2FycmFuZ2UoIE5VTEwsRGVuc2l0eV9wbG90X29mX2NlbnRyYWxpdGllcywgbnJvdyA9IDIsICBoZWlnaHRzPSBjKDEsIC40KSwgbGFiZWxzID0gYygnYScsJ2InKSwKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IC0wLjEsIHZqdXN0ID0gLjgpCgojRGVuc2l0eV9wbG90X29mX2NlbnRyYWxpdGllcy5wYW5lbAoKCmBgYAoKYGBge3J9CgpmYmEgPC0gIHJlYWQuY3N2KCcvaG9tZS9hbGVqYW5kcm8vTkFNVV9pbl9wcm9ncmVzcy9GaWd1cmVzX2NyZWF0aW9uL0NvZGUvUmVzdWx0c19BY2V2ZWRvX2V0X2FsXzIwMjEvMDFfcGhwcHNfYmlwYXJ0aXRlX2ZsdXhlc19zZW5zaXNfb3B0aW1hbGl0eS9GQkFfcmVzdWx0cy5jc3YnKSAlPiUgCiAgICAgICAgc2VsZWN0KGMoJ0lEJywnTmFtZScsICdSZWFjdGlvbicsICdGbHV4JywgJ1NlbnNpdGl2aXR5JykpICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoJ0lEJykKCgoKYXBwZW5kX2ZiYSA8LSBmdW5jdGlvbihjZW50KXsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob2xhIDwtIHJvd25hbWVzX3RvX2NvbHVtbihmYmEpICAlPiUgaW5uZXJfam9pbihyb3duYW1lc190b19jb2x1bW4oY2VudCkpICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoJ3Jvd25hbWUnKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybihob2xhKQp9CgoKCm1hcChsaXN0X29mX2NlbnRzLCBhcHBlbmRfZmJhKSAtPiBmYmFfbGlzdF9vZl9jZW50cwoKCmBgYAoKSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNjYWxlcykKCgphc3NpZ25fbm9kZV90eXBlIDwtIGZ1bmN0aW9uKGRmKXsKZGF0YSA8LSBkZiAgJT4lIHJvd25hbWVzX3RvX2NvbHVtbignSUQnKSAlPiUgZmlsdGVyKCFzdHJfZGV0ZWN0KElELCByZWdleCgnQUFffEFBMicsIGlnbm9yZV9jYXNlID0gRikpKSAgJT4lCiAgICAgICAgIG11dGF0ZShgTm9kZSB0eXBlYCA9IGlmZWxzZShzdHJfZGV0ZWN0KFJlYWN0aW9uLCByZWdleCgnXFxbW2Etel1BXFxdJywgICAgICAgICAgICAgIGlnbm9yZV9jYXNlID0gVCkpLCAnQXN0cm9jeXRlJywgTkEpKSAlPiUgCiAgICAgICAgIG11dGF0ZShgTm9kZSB0eXBlYCA9IGlmZWxzZShzdHJfZGV0ZWN0KFJlYWN0aW9uLCByZWdleCgnXFxbW2Etel1OXFxdJywgICAgICAgICAgICAgIGlnbm9yZV9jYXNlID0gVCkpLCAnTmV1cm9uJywgYE5vZGUgdHlwZWApKSAgICAlPiUgCiAgICAgICAgIG11dGF0ZShgTm9kZSB0eXBlYCA9IGlmZWxzZSggc3RyX2RldGVjdChSZWFjdGlvbiwgcmVnZXgoJ1xcW1thLXpdQVxcXXxcXFtbYS16XU5cXF0nLGlnbm9yZV9jYXNlID0gVCksIG5lZ2F0ZSA9IFQpLCAnRXhjaGFuZ2UnLCBgTm9kZSB0eXBlYCkpICU+JQogICAgICAgICBtdXRhdGUoYE5vZGUgdHlwZWAgPSBpZmVsc2Uoc3RyX2RldGVjdChSZWFjdGlvbiwgcmVnZXgoJ1xcW1thLXpdQVxcXScsaWdub3JlX2Nhc2UgPSBUKSkgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX2RldGVjdChOYW1lLCByZWdleCgnRE1ffERlbWFuZHxzaW5rJyxpZ25vcmVfY2FzZSA9IFQpKSwgJ1NpbmsvRGVtYW5kIEFzdHJvJywgYE5vZGUgdHlwZWApKSU+JQogICAgICAgICBtdXRhdGUoYE5vZGUgdHlwZWAgPSBpZmVsc2Uoc3RyX2RldGVjdChSZWFjdGlvbiwgcmVnZXgoJ1xcW1thLXpdTlxcXScsaWdub3JlX2Nhc2UgPSBUKSkgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX2RldGVjdChOYW1lLCByZWdleCgnRE1ffERlbWFuZHxzaW5rJyxpZ25vcmVfY2FzZSA9IFQpKSwgJ1NpbmsvRGVtYW5kIE5ldXJvbicsIGBOb2RlIHR5cGVgKSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuKGRhdGEpfQoKCgptYXAoZmJhX2xpc3Rfb2ZfY2VudHMsIGFzc2lnbl9ub2RlX3R5cGUpIC0+IGRhdGEKCgpjbGVhbl9jZW50cmFsaXRpZXMgPC0gZnVuY3Rpb24oZGYpeyBkZiAlPiUgc2VsZWN0KC1jICgiSUQiLCAiTmFtZSIsICJSZWFjdGlvbiIsICAiRmx1eCIsICJTZW5zaXRpdml0eSIsIk5vZGUgdHlwZSIpICApfQoKCm1hcChkYXRhLCBjbGVhbl9jZW50cmFsaXRpZXMpIC0+IG9ubHlfY2VudHJhbGl0aWVzCmBgYAoKYGBge3J9CgoKZ2xpbXBzZShkYXRhKQpgYGAKCmBgYHtyfQptYXAoZGF0YSwgY2xlYW5fY2VudHJhbGl0aWVzKSAtPiBvbmx5X2NlbnRyYWxpdGllcwoKCmZpbHRlcl9jZW50cmFsaXRpZXMgPC0gZnVuY3Rpb24oZGYpe2RmICU+JSBzZWxlY3QobWF0Y2hlcygnXmluZm98XmNsb3NlfF5iZXR8XmNvbW11bml8XmthdHp8XnBhZ2UnKSl9IyBtYXRjaGVzKCdeZGVncnxeaGFyfF5pbmZvfF5laWdlbnxebG9hZHxeY2xvc2V8XmJldHxeY29tbXVuaXxecGFnZXxea2F0eicpKX0KCm1hcChvbmx5X2NlbnRyYWxpdGllcywgZmlsdGVyX2NlbnRyYWxpdGllcykgLT4gb25seV9jZW50cmFsaXRpZXMKCgpgYGAKCgpgYGB7cn0KIAoKZ2V0X3RvdGFsX2NlbnRyYWxpdGllcyA8LSBmdW5jdGlvbihkZil7ZGYgJT4lIGFicyAlPiUgcm93U3VtcyB9CgoKbWFwKG9ubHlfY2VudHJhbGl0aWVzLCBnZXRfdG90YWxfY2VudHJhbGl0aWVzKSAtPiB0b3RhbC5hYnMuY2VudHJhbGl0eSAKCmdldF9zdW1tYXJ5emVkLm9wdGltYWxpdHkgPC0gZnVuY3Rpb24oZGYpeyBwc2V1ZG9Mb2cxMCA8LSBmdW5jdGlvbih4KSB7IGFzaW5oKHgvMikvbG9nKDEwKSB9CiAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRmJEZsdXggICAlPiUgYWJzICU+JSBhcy5tYXRyaXgoKSAgJT4lIHBzZXVkb0xvZzEwICAlPiUgcmVzY2FsZShjKDAsMSkpICAtPiBmbHV4ZXMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGYkU2Vuc2l0aXZpdHkgICU+JSBhYnMgJT4lIGFzLm1hdHJpeCgpICAgJT4lIHBzZXVkb0xvZzEwICAlPiUgcmVzY2FsZShjKDAsMSkpLT4gc2Vuc2l0aXZpdGllcyAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZsdXhlcyArIHNlbnNpdGl2aXRpZXMgIC0+IHN1bW1hcnl6ZWQub3B0aW1hbGl0eQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4oc3VtbWFyeXplZC5vcHRpbWFsaXR5KQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAp9CgoKbWFwKCBkYXRhLGdldF9zdW1tYXJ5emVkLm9wdGltYWxpdHkpIC0+IHN1bW1hcnl6ZWQub3B0aW1hbGl0eQoKCgoKYGBgCgpoZWF0bWFwCmBgYHtyIGZpZy5oZWlnaHQ9NS41LCBmaWcud2lkdGg9OS41LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KENvbXBsZXhIZWF0bWFwKQoKbGlicmFyeShXR0NOQSkKCgpnZXRfY29ycmVsYXRpb25fbWF0cmljZXMgPC1mdW5jdGlvbihkZil7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICNzY2FsZV9yb3dzIDwtIGZ1bmN0aW9uKHgpe3Qoc2NhbGUodCh4KSkpfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZiAlPiUgdHJhbnNwb3NlQmlnRGF0YSAgICU+JSBjb3IobWV0aG9kID0gImtlbmRhbGwiKSAtPiBob2xhCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybihob2xhKX0KCm1hcChvbmx5X2NlbnRyYWxpdGllcywgZ2V0X2NvcnJlbGF0aW9uX21hdHJpY2VzKSAgLT4gbXkuY29yci5tYXRyaWNlcwoKCgojYWxsLmVxdWFsKGRhdGEkZm9sZF9jaGFuZ2VfYXJpdG1ldGljJGBOb2RlIHR5cGVgICwgZGF0YSRkZWx0YV9xdWFkcmF0aWMkYE5vZGUgdHlwZWApCiNhbGwuZXF1YWwoZGF0YSRmb2xkX2NoYW5nZV9oYXJtb25pYyRgTm9kZSB0eXBlYCAsIGRhdGEkZGVsdGFfZ2VvbWV0cmljJGBOb2RlIHR5cGVgKQoKbm9kZV90eXBlcyA8LSBkYXRhJGZvbGRfY2hhbmdlX2FyaXRtZXRpYyRgTm9kZSB0eXBlYAoKCgoKCmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQ9NS41LCBmaWcud2lkdGg9OS41LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKc2V0LnNlZWQoMSkKCm15LmNvcnIubWF0cmljZXMkZm9sZF9jaGFuZ2VfZ2VvbWV0cmljIC0+IG15LmNvcnIubWF0CgpsZWZ0X2Fubm90YXRpb24gPC0gcm93QW5ub3RhdGlvbihgTm9kZXNgID0gbm9kZV90eXBlcywgY29sID0gbGlzdCggYE5vZGVzYCA9IGMoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBc3Ryb2N5dGUiICAgICAgICA9ICJkZWVwc2t5Ymx1ZTMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0V4Y2hhbmdlJyAgID0gJ2RhcmtncmVlbicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdTaW5rL0RlbWFuZCBBc3Rybyc9J2Rhcmtnb2xkZW5yb2QnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTmV1cm9uIiAgICAgICAgICAgPSAiYnJvd24yIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ1NpbmsvRGVtYW5kIE5ldXJvbic9ICdibHVldmlvbGV0JyApKSkKCnRvdGFsLmFicy5jZW50cmFsaXR5JGZvbGRfY2hhbmdlX2FyaXRtZXRpYyAgLT4gYV90b3RhbC5hYnMuY2VudApzdW1tYXJ5emVkLm9wdGltYWxpdHkkZm9sZF9jaGFuZ2VfYXJpdG1ldGljIC0+IGFfc3VtbWFyeXplZC5vcHRpCgpyaWdodF9hbm5vdGF0aW9uIDwtICAgCiAgcm93QW5ub3RhdGlvbihnYXAgPSB1bml0KDEyLCAicG9pbnRzIiksQ2UgICAgICA9IGFubm9fYmFycGxvdChiYXJfd2lkdGggPSAwLjAxLHdpZHRoID0gdW5pdCgxLjUsICJjbSIpLCBib3JkZXIgPSBULGFfdG90YWwuYWJzLmNlbnQsIGdwID0gZ3Bhcihjb2wgPSAnYXp1cmU0JykpLAogICAgICAgICAgICBPcCAgICAgICAgPSBhbm5vX2JhcnBsb3QoYmFyX3dpZHRoID0gMC4wMSx3aWR0aCA9IHVuaXQoMS41LCAiY20iKSAsYm9yZGVyID0gVCwgYV9zdW1tYXJ5emVkLm9wdGksIGdwID0gZ3Bhcihjb2wgPSAnYXp1cmU0JykpKQoKCmh0IDwtIEhlYXRtYXAobXkuY29yci5tYXQsIG5hbWUgPSAiQ29ycmVsYXRpb24iLCAgbGVmdF9hbm5vdGF0aW9uID1sZWZ0X2Fubm90YXRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmlnaHRfYW5ub3RhdGlvbj1yaWdodF9hbm5vdGF0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyaW5nX2Rpc3RhbmNlX2NvbHVtbnMgID0gZnVuY3Rpb24obSkgICBkaXN0KG0sIG1ldGhvZCA9ICdldWNsaWRlYW4nKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9jb2x1bW5zICAgICAgICAgICAgICA9IGZ1bmN0aW9uKHgpIGZhc3RjbHVzdGVyOjpoY2x1c3QoZGlzdCh4KSwgIm1lZGlhbiIpLAogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyAgID0gZnVuY3Rpb24obSkgICBkaXN0KG0sIG1ldGhvZCA9ICdldWNsaWRlYW4nKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzICAgICAgICAgICAgICAgPSBmdW5jdGlvbih4KSBmYXN0Y2x1c3Rlcjo6aGNsdXN0KGRpc3QoeCksICJtZWRpYW4iKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3dfa20gPSAyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl9rbSA9IDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyByb3dfc3BsaXQgPSBwYXN0ZTAoImNsdXN0ZXIgIiwgcGEkY2x1c3RlcmluZyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBjb2x1bW5fc3BsaXQgPSBwYXN0ZTAoImNsdXN0ZXIgIiwgcGEkY2x1c3RlcmluZyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlciA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJvd19kZW5kX3dpZHRoICAgID0gdW5pdCgzLCAiY20iKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93X2dhcCA9IHVuaXQoMiwgIm1tIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl9nYXAgPSB1bml0KDIsICJtbSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSB1bml0KDEwLCAiY20iKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVpZ2h0ID0gdW5pdCgxMCwgImNtIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sdW1uX3RpdGxlID0gYygiQXN0cm9jeXRpYyBjbHVzdGVyIiwgIk5ldXJvbmFsIGNsdXN0ZXIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5fdGl0bGVfZ3AgPSBncGFyKGZvbnRzaXplID0gMTApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93X3RpdGxlX3JvdCAgPSAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2NvbHVtbl9uYW1lcyAgICA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvd19yb3dfbmFtZXMgICAgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3dfbmFtZXNfZ3AgPSBncGFyKGZvbnRzaXplID0gOCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3dfdGl0bGUgPSBjKCJBc3Ryb2N5dGljXG4gY2x1c3RlciIsICJOZXVyb25hbFxuIGNsdXN0ZXIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm93X3RpdGxlX2dwID0gZ3Bhcihmb250c2l6ZSA9IDEwKSkKbXlfaGVhdG1hcCA9IGdyaWQuZ3JhYkV4cHIoZHJhdyhodCkpCgpodApgYGAKCgoKCmBgYHtyIGZpZy5oZWlnaHQ9NS41LCBmaWcud2lkdGg9MTR9CiB1cHBlcl9wYW5lbCA8LSBnZ2FycmFuZ2UoIERlbnNpdHlfcGxvdF9vZl9jZW50cmFsaXRpZXMucGFuZWwsICBteV9oZWF0bWFwLCBuY29sID0gMiwgd2lkdGhzID0gYyguNCwgMSksIGhlaWdodHMgPSBjKDEsLjMpLCBsYWJlbHMgPSBjKCcnLCdjJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IC00LCB2anVzdCA9IC44KQoKdXBwZXJfcGFuZWwKYGBgCgpEaXN0cmlidXRpb24gb2YgcGFpcndpc2UgY29ycmVsYXRpb25zCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJvd19vcmRlcihodClbWzFdXSAtPiBhc3Ryb19jbHVzdGVyCnJvd19vcmRlcihodClbWzJdXSAtPiBuZXVyb25fY2x1c3RlcgpkYXRhJGBOb2RlIHR5cGVgICAgLT4gTm9kZXMKZGF0YSRJRFtuZXVyb25fY2x1c3Rlcl0gICAgIC0+IG5ldXJvbl9jbHVzdGVyX25hbWVzCmRhdGEkSURbYXN0cm9fY2x1c3Rlcl0gICAgICAtPiBhc3Ryb19jbHVzdGVyX25hbWVzCmh0QG1hdHJpeCAlPiUgYXMuZGF0YS5mcmFtZSAtPiBoZWF0bWFwX21hdHJpeAoKaGVhdG1hcF9tYXRyaXhbbmV1cm9uX2NsdXN0ZXJfbmFtZXMsbmV1cm9uX2NsdXN0ZXJfbmFtZXNdICU+JSBhcy5tYXRyaXggJT4lIGMgLT4gYE5ldXJvbmFsX2NsdXN0ZXJgCmhlYXRtYXBfbWF0cml4W2FzdHJvX2NsdXN0ZXJfbmFtZXMsYXN0cm9fY2x1c3Rlcl9uYW1lc10gJT4lIGFzLm1hdHJpeCAlPiUgYyAgLT5gQXN0cm9jeXRpY19jbHVzdGVyYCAKaGVhdG1hcF9tYXRyaXhbYXN0cm9fY2x1c3Rlcl9uYW1lcyxuZXVyb25fY2x1c3Rlcl9uYW1lc10gJT4lIGFzLm1hdHJpeCAlPiUgYyAgLT4gYE5ldXJvbl92c19Bc3Ryb2N5dGVgCgpkYXRhLmZyYW1lKGBOZXVyb25hbF9jbHVzdGVyYCkgJT4lIGdhdGhlciAgLT4gQQpkYXRhLmZyYW1lKGBBc3Ryb2N5dGljX2NsdXN0ZXJgKSAlPiUgZ2F0aGVyICAgLT4gQgpkYXRhLmZyYW1lKCBgTmV1cm9uX3ZzX0FzdHJvY3l0ZWApICU+JSBnYXRoZXIgLT4gQwpxdWFkcyA8LSByYmluZChBLEIsQykKY29sbmFtZXMocXVhZHMpIDwtIGMoIkNvbXBhcmlzb24iLCJOb2RlIGNvcnJlbGF0aW9uIikKCiNxdWFkcyRDb21wYXJpc29uICU+JSB1bmlxdWUoKQoKbXlfY29tcGFyaXNvbnMgPC0gbGlzdCggYygiTmV1cm9uYWxfY2x1c3RlciIsICJBc3Ryb2N5dGljX2NsdXN0ZXIiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIGMoIk5ldXJvbmFsX2NsdXN0ZXIiLCAiTmV1cm9uX3ZzX0FzdHJvY3l0ZSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgYygiQXN0cm9jeXRpY19jbHVzdGVyIiwgIk5ldXJvbl92c19Bc3Ryb2N5dGUiKSApCmBgYApgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0zfQpnZ3Bsb3QocXVhZHMsIGFlcyh4PUNvbXBhcmlzb24sIHk9YE5vZGUgY29ycmVsYXRpb25gLCBmaWxsPUNvbXBhcmlzb24pKSArIAogICAgZ2VvbV92aW9saW4odHJpbT1GLCBjb2xvdXIgPSAiYXp1cmU0IikrICAKICAgIHN0YXRfY29tcGFyZV9tZWFucyggdmp1c3Q9IC0xLiwgaGp1c3Q9IDAsIGNvbXBhcmlzb25zID0gbXlfY29tcGFyaXNvbnMsIG1ldGhvZCA9ICJ3aWxjb3gudGVzdCIsIHAuYWRqdXN0Lm1ldGhvZCA9ICJib25mZXJyb25pIiwgbGFiZWwgPSAicC5zaWduaWYiKSsKICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYWxwaGEoYygiZGVlcHNreWJsdWUzIiwgImJsdWV2aW9sZXQiLCAiYnJvd24yIiksIC44KSkgKyAKICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksbGVnZW5kLnBvc2l0aW9uPSJub25lIiwgIGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNTAsIGhqdXN0ID0gMSkpICArIAogICNnZ3RpdGxlKCJOZXVyb25hbCBzZWxmLWNvcnJlbGF0aW9ucyIpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKC0wLjcsIDIpKSArIGdlb21faGxpbmUoeWludGVyY2VwdD0wLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3IgPSAiZGFya3JlZCIsICBzaXplPTEpLT4gY29ycl9jb21wYXJpc29ucwoKY29ycl9jb21wYXJpc29ucwpgYGAKCgpgYGB7cn0KbGlicmFyeShQcm9qZWN0aW9uQmFzZWRDbHVzdGVyaW5nKQpsaWJyYXJ5KFBDQXRvb2xzKQpsaWJyYXJ5KG1hZ3JpdHRyKQoKcCA8LSBwY2EobXkuY29yci5tYXQpCgpjYmluZChwJHJvdGF0ZWQkUEMxLCBwJHJvdGF0ZWQkUEMyKSAlPiUgYXMubWF0cml4ICU+JSBhcy5kYXRhLmZyYW1lLT4gbXlfcGNhCgoKCgpteV9wY2EgJT4lICBjYmluZCggZGF0YSRgTm9kZSB0eXBlYCkgJT4lIHNldF9jb2xuYW1lcyhjKCdQQzEnLCdQQzInLCdub2RlJykpIC0+IHRvLnBjYS5zY2F0dGVyCgpzdW1tYXJ5emVkLm9wdGltYWxpdHkgLT4gT3AKdG90YWwuYWJzLmNlbnRyYWxpdHkgLT4gQ2VudHJhbGl0eQpDZW50cmFsaXR5IC0+IENlCgpiIDwtIGdncGxvdCh0by5wY2Euc2NhdHRlciwgYWVzKHggPSBQQzEsIHkgPSBQQzIpKSAKYiArICAgc2NhbGVfY29sb3JfbWFudWFsKGxhYmVscyA9IGMoIkFzdHJvIiwgIkV4Y2giLCdOZXUnLCdTaS9EZSBBc3QnLCAnU2kvRGUgTmV1JyksIHZhbHVlcyA9ICBjKCJkZWVwc2t5Ymx1ZTMiLCdkYXJrZ3JlZW4nLCdicm93bjInLCdkYXJrZ29sZGVucm9kJywnYmx1ZXZpb2xldCcpKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0icmlnaHQiLCBsZWdlbmQuYm94ID0gInZlcnRpY2FsIikrIGdlb21fcG9pbnQoYWVzKHNpemUgPSBPcCwgY29sb3IgPSBub2RlKSktPiBwY2Ffbm9kZV9SZWRDb3N0cwoKCgoKYiA8LSBnZ3Bsb3QodG8ucGNhLnNjYXR0ZXIsIGFlcyh4ID0gUEMxLCB5ID0gUEMyKSkgCmIgKyAgIHNjYWxlX2NvbG9yX21hbnVhbChsYWJlbHMgPSBjKCJBc3RybyIsICJFeGNoIiwnTmV1JywnU2kvRGUgQXN0JywgJ1NpL0RlIE5ldScpLCB2YWx1ZXMgPSAgYygiZGVlcHNreWJsdWUzIiwnZGFya2dyZWVuJywnYnJvd24yJywnZGFya2dvbGRlbnJvZCcsJ2JsdWV2aW9sZXQnKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0icmlnaHQiLCBsZWdlbmQuYm94ID0gInZlcnRpY2FsIikrIGdlb21fcG9pbnQoYWVzKCBzaXplID0gIENlLGNvbG9yID0gbm9kZSkpLT4gcGNhX25vZGVfY2VudHJhbGl0eQpgYGAKCmBgYHtyfQoKCmJvdHRvbV9wYW5lbCA8LSBnZ2FycmFuZ2UoIGNvcnJfY29tcGFyaXNvbnMscGNhX25vZGVfY2VudHJhbGl0eSwgcGNhX25vZGVfUmVkQ29zdHMgICwgbmNvbCA9IDMsIHdpZHRocyAgPSBjKC40LDEsMSkgLCBsYWJlbHMgPSBjKCdkJywnZScsJ2YnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMCwgdmp1c3QgPSAxKQoKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTksIGZpZy53aWR0aD0xM30KCgpwYW5lbF93aXRob3V0X2dyYXBoIDwtICBnZ2FycmFuZ2UodXBwZXJfcGFuZWwsIGJvdHRvbV9wYW5lbCwgbnJvdyA9IDIsIGhlaWdodHMgPSBjKDEsMC41KSkKcGFuZWxfd2l0aG91dF9ncmFwaAoKYGBgCgpgYGB7cn0KZ2dzYXZlKGZpbGU9InBhbmVsX3dpdGhvdXRfZ3JhcGgucG5nIiwgcGxvdD1wYW5lbF93aXRob3V0X2dyYXBoLCB3aWR0aD0xMywgaGVpZ2h0PTksIGRwaSA9IDMyMCkKYGBgCgoKTm9kZSB0eXBlcyBmb3IgTmV0d29ya3ggKHB5dGhvbikgYXR0cmlidXRlcwpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShtYWdyaXR0cikKClJlc3VsdHMgPC0gcmVhZF9jc3YoIlJlc3VsdHMuY3N2IikKCmRhdGFfTmV0d29ya3ggPC0gUmVzdWx0cyU+JSAKICAgICAgICAgbXV0YXRlKGBOb2RlX3R5cGVgID0gaWZlbHNlKHN0cl9kZXRlY3QoRm9ybXVsYSwgcmVnZXgoJ1xcW1thLXpdQVxcXScsICAgICAgICAgICAgICBpZ25vcmVfY2FzZSA9IFQpKSwgJ0FzdHJvY3l0ZScsIE5BKSkgJT4lIAogICAgICAgICBtdXRhdGUoYE5vZGVfdHlwZWAgPSBpZmVsc2Uoc3RyX2RldGVjdChGb3JtdWxhLCByZWdleCgnXFxbW2Etel1OXFxdJywgICAgICAgICAgICAgIGlnbm9yZV9jYXNlID0gVCkpLCAnTmV1cm9uJywgYE5vZGVfdHlwZWApKSAgICAlPiUgCiAgICAgICAgIG11dGF0ZShgTm9kZV90eXBlYCA9IGlmZWxzZSggc3RyX2RldGVjdChGb3JtdWxhLCByZWdleCgnXFxbW2Etel1BXFxdfFxcW1thLXpdTlxcXScsaWdub3JlX2Nhc2UgPSBUKSwgbmVnYXRlID0gVCksICdFeGNoYW5nZScsIGBOb2RlX3R5cGVgKSkgJT4lCiAgICAgICAgIG11dGF0ZShgTm9kZV90eXBlYCA9IGlmZWxzZShzdHJfZGV0ZWN0KEZvcm11bGEsIHJlZ2V4KCdcXFtbYS16XUFcXF0nLGlnbm9yZV9jYXNlID0gVCkpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cl9kZXRlY3QoTmFtZSwgcmVnZXgoJ0RNX3xEZW1hbmR8c2luaycsaWdub3JlX2Nhc2UgPSBUKSksICdTaW5rL0RlbWFuZCBBc3RybycsIGBOb2RlX3R5cGVgKSklPiUKICAgICAgICAgbXV0YXRlKGBOb2RlX3R5cGVgID0gaWZlbHNlKHN0cl9kZXRlY3QoRm9ybXVsYSwgcmVnZXgoJ1xcW1thLXpdTlxcXScsaWdub3JlX2Nhc2UgPSBUKSkgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX2RldGVjdChOYW1lLCByZWdleCgnRE1ffERlbWFuZHxzaW5rJyxpZ25vcmVfY2FzZSA9IFQpKSwgJ1NpbmsvRGVtYW5kIE5ldXJvbicsIGBOb2RlX3R5cGVgKSkKCgpkYXRhX05ldHdvcmt4ICU8PiUgc2VsZWN0KGMoSUQsIE5vZGVfdHlwZSkpCgp3cml0ZV9jc3YoZGF0YV9OZXR3b3JreCwgJ2RhdGFfTmV0d29ya3guY3N2JykKCmBgYAoKCgoKCgoK